package com.example.demo.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {
    public static void main(String[] args) {
        // 只用来处理连接请求的线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(10);
        // 用来处理业务请求（读写）的线程组
        EventLoopGroup workGroup = new NioEventLoopGroup(10);
        try {
            // 配置启动对象
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup) // 将线程组配置进去
                    .channel(NioServerSocketChannel.class) // 设置这里要用什么通道,这里配置的是服务器通道
                    // 设置如果线程池满了,将连接请求暂存到队列中排队,队列长度位1024（自己指定合适长度）
                    .option(ChannelOption.SO_BACKLOG,1024)
                    // 添加处理器来执行对应业务(这个方式基本就是像下面的模板不变，传入自己的处理器类即可)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        // 这里的SocketChannel就是连接的那个客户端和服务端的通道对象
                        protected void initChannel(SocketChannel socketChannel){
                            // 通过pipeline方法拿到ChannelPipeline 对象(它负责所有事件的处理)
                            // 然后把自己的事件处理类添加进去
                            // 这里传入自己的事件处理类相当于拦截器,（类似SpringMVC那种）可以传入多个拦截器
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 添加解码器,里面默认UTF-8方式解码
                            pipeline.addLast(new StringDecoder())
                                    // 这个是编码器,也是默认的UTF-8
                                    .addLast(new StringEncoder())
                                    .addLast(new NettyServerHandler());

                        }
                    });
            // 指定端口启动服务器，并且设置sync表示用阻塞模式启动
            // 否则如果接着又一些操作的话会造成服务还没启动操作就开始运行了，显然就出问题了
            // ChannelFuture就相当于监听器,可以通过它添加一些方法实现监听(类似安卓中添加的addOnClickListener)
            // 比如说根据连接的结果执行不同的代码,成功就怎么怎么,失败就怎么怎么
            ChannelFuture channelFuture= serverBootstrap.bind(7000).sync();
            // 设置检测与客户端的通道是否关闭,如果已经关闭则继续往下执行
            // 加上sync表示如果没有关闭则将线程阻塞在这里不要往下走了
            // 通过上面返回的这个对象的channel方法就可以拿到服务器和这个客户端连接的Channel通道
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 当执行到这里说明通道已经关闭了,那么就关闭线程组资源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }


    }
}
